package common

import (
	"errors"
	"os"
	"reflect"
	"regexp"
	"sort"
	"time"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/config"
	"github.com/astaxie/beego/logs"
	"github.com/astaxie/beego/orm"
)

const DATE_FORMAT = "2006-01-02 15:04:05"
const DATE_T_FORMAT = "2006-01-02T15:04:05"

func DesString(dbpwd string) (strs string) {
	defer Catchs()
	if "" != dbpwd || len(dbpwd) > 0 {
		key := beego.AppConfig.String("key")
		key1 := []byte(key)
		bytes, _ := DePwdCode(dbpwd, key1)
		strs = string(bytes)
	} else {
		strs = ""
	}
	return strs
}

func AesString(dbpwd string) (strs string) {
	defer Catchs()
	pwd := []byte{}
	if dbpwd == "" {
		pwd = []byte("123")
	} else {
		pwd = []byte(dbpwd)
	}
	key := []byte(beego.AppConfig.String("key"))
	strs, err := EnPwdCode(pwd, key)
	if err != nil {
		logs.Error(err)
	} else {
		logs.Info(strs)
	}
	return strs
}

func GetTokenExpirTime() (token_expir_time int) {
	token_expir_time, err := beego.AppConfig.Int("token_expir_time")
	if err == nil {
		return token_expir_time
	}
	return 3
}

func GetCurTime() string {
	return time.Now().Format(DATE_FORMAT)
}

func GetCurDate() string {
	return time.Now().Format("2006-01-02")
}
func GetCurDateFormat() string {
	return time.Now().Format("2006/01/02")
}

func GetBeforeTime(days int) string {
	nowTime := time.Now()
	getTime := nowTime.AddDate(0, 0, days)
	resTime := getTime.Format(DATE_FORMAT) // The format of the obtained time
	logs.Info("Get the number of days：", days, ",Time ago：", resTime)
	return resTime
}

func GetSpecifiedTime(startTime time.Time, days int, localFlag, isTparam bool) string {
	getTime := startTime.AddDate(0, 0, days)
	resTime := ""
	if !localFlag {
		h, _ := time.ParseDuration("1h")
		if isTparam {
			resTime = getTime.Add(8 * h).Format(DATE_T_FORMAT)
		} else {
			resTime = getTime.Add(8 * h).Format(DATE_FORMAT)
		}
	} else {
		if isTparam {
			resTime = getTime.Format(DATE_T_FORMAT) // The format of the obtained time
		} else {
			resTime = getTime.Format(DATE_FORMAT) // The format of the obtained time
		}
	}
	logs.Info("Get the number of days: ", days, ",Time: ", resTime)
	return resTime
}

func Catchs() {
	if err := recover(); err != nil {
		logs.Error("The program is abnormal, err: ", err)
	}
}

// Time string to timestamp
func PraseTimeInt(stringTime time.Time) int64 {
	timeStr := stringTime.String()
	if timeStr != "" && len(timeStr) > 19 {
		timeStr = timeStr[:19]
	}
	loc, _ := time.LoadLocation("Local")
	theTime, err := time.ParseInLocation(DATE_FORMAT, timeStr, loc)
	if err == nil {
		unixTime := theTime.Unix()
		return unixTime
	} else {
		logs.Error(err)
	}
	return 0
}

// Get current timestamp
func CurTimestamp() int64 {
	timeStamp := time.Now().Unix()
	return timeStamp
}

// Does the string slice contain a certain value
func IsValueInList(value string, list []string) bool {
	for _, v := range list {
		if v == value {
			return true
		}
	}
	return false
}

func GetLocalCurTime() string {
	localTime := time.Unix(time.Now().Unix(), 0)
	local, _ := time.LoadLocation("Asia/Shanghai")
	timeFormat := DATE_FORMAT
	return localTime.In(local).Format(timeFormat)
}

func TimeConverStr(timeStr string) string {
	times, _ := time.Parse(DATE_FORMAT, timeStr)
	timeUnix := times.Unix()
	timx := time.Unix(timeUnix, 0).Format(DATE_FORMAT)
	return timx
}

func GetSpecialDate(beforeDate int) string {
	year, month, date := time.Now().Date()
	thisMonth := time.Date(year, month, beforeDate, 0, 0, 0, 0, time.Local)
	var startDate time.Time
	var curTime string
	if date > beforeDate {
		startDate = thisMonth.AddDate(0, 0, 0)
	} else {
		startDate = thisMonth.AddDate(0, -1, 0)
	}
	h, _ := time.ParseDuration("-1h")
	curTime = startDate.Add(8 * h).Format(DATE_FORMAT)
	return curTime
}

func GetBeforeDate(flag int, value int) string {
	getDate := ""
	// flag: 0: current date; 1: previous day's date; 2: money one month's date; 3: previous year's date
	now := time.Now()
	switch flag {
	case 0:
		curDate := now.Format(DATE_FORMAT)
		if len(curDate) > 1 {
			getDate = curDate
		}
	case 1:
		beforeDay := now.AddDate(0, 0, value)
		getDate = beforeDay.Format(DATE_FORMAT)
	case 2:
		beforeMonth := now.AddDate(0, value, 0)
		getDate = beforeMonth.Format(DATE_FORMAT)
	case 3:
		beforeYear := now.AddDate(value, 0, 0)
		getDate = beforeYear.Format(DATE_FORMAT)
	}
	logs.Info("getDate: ", getDate)
	return getDate
}

func DeletePreAndSufSpace(str string) string {
	strList := []byte(str)
	spaceCount, count := 0, len(strList)
	for i := 0; i <= len(strList)-1; i++ {
		if strList[i] == 32 {
			spaceCount++
		} else {
			break
		}
	}
	strList = strList[spaceCount:]
	spaceCount, count = 0, len(strList)
	for i := count - 1; i >= 0; i-- {
		if strList[i] == 32 {
			spaceCount++
		} else {
			break
		}
	}
	return string(strList[:count-spaceCount])
}

type CveDescription struct {
	EnDesc string `json:"en"`
	ZhDesc string `json:"zh"`
}

type NodeCpe struct {
	Cpe23Uri       string `json:"cpe23Uri"`
	CpeMatchString string `json:"cpeMatchString"`
	Vulnerable     string `json:"vulnerable"`
}

type ConfNodes struct {
	Operator string    `json:"operator"`
	Cpe      []NodeCpe `json:"cpe"`
}

type CveConfigurations struct {
	Nodes []ConfNodes `json:"nodes"`
}

type BmCvssV3 struct {
	VectorString          string  `json:"vectorString"`
	AttackComplexity      string  `json:"attackComplexity"`
	AttackVector          string  `json:"attackVector"`
	AvailabilityImpact    string  `json:"availabilityImpact"`
	BaseSeverity          string  `json:"baseSeverity"`
	UserInteraction       string  `json:"userInteraction"`
	BaseScore             float64 `json:"baseScore"`
	PrivilegesRequired    string  `json:"privilegesRequired"`
	Version               string  `json:"version"`
	ConfidentialityImpact string  `json:"confidentialityImpact"`
	IntegrityImpact       string  `json:"integrityImpact"`
	Scope                 string  `json:"scope"`
}

type ImBaseMetricV3 struct {
	CvssV3              BmCvssV3 `json:"cvssV3"`
	ImpactScore         float64  `json:"impactScore"`
	ExploitabilityScore float64  `json:"exploitabilityScore"`
}

type BmCvssV2 struct {
	VectorString          string  `json:"vectorString"`
	AccessComplexity      string  `json:"accessComplexity"`
	AvailabilityImpact    string  `json:"availabilityImpact"`
	Authentication        string  `json:"authentication"`
	Version               string  `json:"version"`
	BaseScore             float64 `json:"baseScore"`
	IntegrityImpact       string  `json:"integrityImpact"`
	ConfidentialityImpact string  `json:"confidentialityImpact"`
	AccessVector          string  `json:"accessVector"`
}

type ImBaseMetricV2 struct {
	AcInsufInfo             string   `json:"acInsufInfo"`
	CvssV2                  BmCvssV2 `json:"cvssV2"`
	UserInteractionRequired string   `json:"userInteractionRequired"`
	Severity                string   `json:"severity"`
	ObtainUserPrivilege     string   `json:"obtainUserPrivilege"`
	ObtainAllPrivilege      string   `json:"obtainAllPrivilege"`
	ImpactScore             float64  `json:"impactScore"`
	ExploitabilityScore     float64  `json:"exploitabilityScore"`
	ObtainOtherPrivilege    string   `json:"obtainOtherPrivilege"`
}

type CveImpact struct {
	BaseMetricV3 ImBaseMetricV3 `jsong:"baseMetricV3"`
	BaseMetricV2 ImBaseMetricV2 `jsong:"baseMetricV2"`
}

type CvePoc struct {
	Source  string `json:"source"`
	Date    string `json:"date"`
	Path    string `json:"path"`
	Dbindex string `json:"dbindex"`
	Url     string `json:"url"`
	Desc    string `json:"desc"`
}

type CveEvent struct {
	Title       string `json:"title"`
	Date        string `json:"date"`
	Description string `json:"description"`
	Url         string `json:"url"`
}

type CveReferenceData struct {
	Url       string   `json:"url"`
	Name      string   `json:"name"`
	Refsource string   `json:"refsource"`
	Tags      []string `json:"tags"`
	SourceUrl string   `json:"source_url"`
}

type CveVulType struct {
	Cwe string `json:"cwe"`
	En  string `json:"en"`
	Zh  string `json:"zh"`
}

type FixReferences struct {
	Url       string   `json:"url"`
	Refsource string   `json:"refsource"`
	Name      string   `json:"name"`
	Tags      []string `json:"tags"`
}

type CveFixSuggest struct {
	Detail     string          `jsong:"detail"`
	References []FixReferences `jsong:"references"`
}

type CveOriginData struct {
	Ids            string             `json:"ids"`
	CveNum         string             `json:"cveNum"`
	UpdateType     string             `json:"updateType"`
	CvePackName    []string           `json:"cvePackName"`
	PackName       []string           `json:"packName"`
	Description    CveDescription     `json:"description"`
	Title          string             `json:"title"`
	AffectProduct  []string           `json:"affectProduct"`
	Configurations CveConfigurations  `json:"configurations"`
	CnnvdID        string             `json:"cnnvdID"`
	CnvdID         string             `json:"cnvdID"`
	PublishedDate  string             `json:"publishedDate"`
	GetTime        string             `json:"getTime"`
	EndGetTime     string             `json:"endGetTime"`
	Impact         CveImpact          `json:"impact"`
	VulStatus      string             `json:"vulStatus"`
	Poc            CvePoc             `json:"poc"`
	Event          []CveEvent         `json:"event"`
	ReferenceData  []CveReferenceData `json:"referenceData"`
	VulType        []CveVulType       `json:"vulType"`
	FixSuggest     CveFixSuggest      `json:"fixSuggest"`
	Version        string             `json:"version"`
	Credibility    int                `json:"credibility"`
	Patch          []CveOriginPatch   `json:"patch"`
}

type SbomReq struct {
	Coordinates []string `json:"coordinates"`
}

type UploadData struct {
	Token   string `json:"Token"`
	Source  int    `json:"source"`
	CveData []CveOriginData
}

type CveOriginDetailData struct {
	CveNum         string             `json:"cveNum"`
	CvePackName    string             `json:"cvePackName"`
	Description    CveDescription     `json:"description"`
	Title          string             `json:"title"`
	Configurations CveConfigurations  `json:"configurations"`
	CnnvdID        string             `json:"cnnvdID"`
	CnvdID         string             `json:"cnvdID"`
	PublishedDate  string             `json:"publishedDate"`
	GetTime        string             `json:"getTime"`
	Impact         CveImpact          `json:"impact"`
	VulStatus      string             `json:"vulStatus"`
	Poc            CvePoc             `json:"poc"`
	Event          []CveEvent         `json:"event"`
	ReferenceData  []CveReferenceData `json:"referenceData"`
	VulType        []CveVulType       `json:"vulType"`
	FixSuggest     CveFixSuggest      `json:"fixSuggest"`
	Patch          []CveOriginPatch   `json:"patch"`
}

type CveOriginPatch struct {
	Package    string `json:"package"`
	FixVersion string `json:"fixversion"`
	FixPatch   string `json:"fix_patch"`
	BreakPatch string `json:"break_patch"`
	Source     string `json:"source"`
	Branch     string `json:"branch"`
}

func GetRepoOrg() (string, error) {
	BConfig, err := config.NewConfig("ini", "conf/app.conf")
	if err != nil {
		logs.Error("config init error:", err)
		return "", err
	}
	owner := BConfig.String("gitee::owner")
	if owner == "" {
		logs.Error("config gitee::owner error: invalid value is ", owner)
		return "", errors.New("value is nil")
	}
	return owner, nil
}

func CompareSlice(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	if (a == nil) != (b == nil) {
		return false
	}
	bFlag := false
	for _, av := range a {
		for _, bv := range b {
			if av == bv {
				bFlag = true
				break
			} else {
				bFlag = false
			}
		}
	}
	return bFlag
}

func CreateDir(dir string) error {
	_, err := os.Stat(dir)
	if err != nil {
		if os.IsNotExist(err) {
			os.Mkdir(dir, 0777)
		}
	}
	return err
}

func CreateAllDir(dir string) error {
	_, err := os.Stat(dir)
	if err != nil {
		if os.IsNotExist(err) {
			os.MkdirAll(dir, 0777)
		}
	}
	return err
}

func StrFirstToUpper(str string) string {
	var upperStr string
	vv := []rune(str)
	for i := 0; i < len(vv); i++ {
		if i == 0 {
			vv[i] -= 32
			upperStr += string(vv[i]) // + string(vv[i+1])
		} else {
			upperStr += string(vv[i])
		}
	}
	return upperStr
}

func BranchVersionRep(text string) string {
	reg := regexp.MustCompile(`(\(.*\))|(（.*）)`)
	return reg.ReplaceAllString(text, "")
}

func StripStrReg(text string) string {
	reg := regexp.MustCompile(`^[a-zA-Z]`)
	return reg.ReplaceAllString(text, "")
}

func FindSliceEm(slice []string, val string) (int, bool) {
	if len(slice) == 0 {
		return -1, false
	}
	for i, item := range slice {
		if item == val {
			return i, true
		}
	}
	return -1, false
}

func GetBeforeHourTime(hours time.Duration) string {
	now := time.Now()
	h, _ := time.ParseDuration("-1h")
	beforeHours := now.Add(hours * h).Format(DATE_FORMAT)
	return beforeHours
}

func SliceRemoveDup(req interface{}) (ret []interface{}) {
	if reflect.TypeOf(req).Kind() != reflect.Slice {
		return
	}
	value := reflect.ValueOf(req)
	for i := 0; i < value.Len(); i++ {
		if i > 0 && reflect.DeepEqual(value.Index(i-1).Interface(), value.Index(i).Interface()) {
			continue
		}
		ret = append(ret, value.Index(i).Interface())
	}
	return
}

func RemoveDupString(tagsList []string) []string {
	repList := make([]string, 0)
	if len(tagsList) > 0 {
		sort.Strings(tagsList)
		tmpList := SliceRemoveDup(tagsList)
		for _, tl := range tmpList {
			repList = append(repList, tl.(string))
		}
	}
	return repList
}

func IsContainsLetters(str string) bool {
	if len(str) > 0 {
		if isOk, _ := regexp.MatchString("[a-zA-Z-_]+", str); isOk {
			return true
		}
	} else {
		return true
	}
	return false
}

func IsContainSpecialChar(str string, flag int8) bool {
	if len(str) > 0 {
		if flag == 1 {
			if isOk, _ := regexp.MatchString("^[r|R][0-9]+(.)*[0-9]$", str); isOk {
				return true
			}
		} else {
			if isOk, _ := regexp.MatchString("^[v|V][0-9]+(.)*[0-9]$", str); isOk {
				return true
			}
		}
	} else {
		return true
	}
	return false
}

// Obtain token data from different communities and extract them into public data
func GetOwnerAndToken(cve string, organizationID int8, flag ...int64) (owner, accessToken string) {
	if organizationID == 2 {
		owner = beego.AppConfig.String("opengauss::gauss_owner")
		accessToken = beego.AppConfig.String("opengauss::git_gauss_token")
	} else if organizationID == 3 {
		owner = beego.AppConfig.String("mindspore::mindspore_owner")
		accessToken = beego.AppConfig.String("mindspore::git_mindspore_token")
	} else if organizationID == 4 {
		owner = beego.AppConfig.String("openlookeng::openlookeng_owner")
		accessToken = beego.AppConfig.String("openlookeng::git_openlookeng_token")
	} else {
		owner = beego.AppConfig.String("gitee::owner")
		accessToken = beego.AppConfig.String("gitee::git_token")
	}
	if len(flag) > 0 {
		if GetCveSource(cve) == 1 || GetCveSourceRecord(cve) == 1 {
			accessToken = beego.AppConfig.String("majun::token")
		}
	}
	return
}

type Source struct {
	Source int `orm:"column(source)"`
}

func GetCveSource(cve string) (source int) {
	source = 0
	if len(cve) == 0 {
		return
	}
	o := orm.NewOrm()
	var res Source
	err := o.Raw("select source from cve_origin_upstream where cve_num = ?", cve).QueryRow(&res)
	if err != nil {
		logs.Error(err)
		return
	}
	source = res.Source
	return
}

func ValidateMajunPack(cve, pack string) bool {
	o := orm.NewOrm()
	var res Source
	err := o.Raw("select source from cve_origin_upstream where cve_num = ? and git_packname like ?", cve, "%"+pack+"%").QueryRow(&res)
	if err != nil {
		logs.Error(err)
		return false
	}
	return true
}

func GetCveSourceRecord(cve string) (source int) {
	source = 0
	if len(cve) == 0 {
		return
	}
	o := orm.NewOrm()
	var res Source
	err := o.Raw("select source from cve_origin_upstream_record where cve_num = ? ORDER BY cve_record_id DESC", cve).QueryRow(&res)
	if err != nil {
		logs.Error(err)
		return
	}
	source = res.Source
	return
}

func DeleteSliceValue(org []string, value string) (repSlice []string) {
	repSlice = org
	for k, v := range org {
		if v == value {
			repSlice = append(org[:k], org[k+1:]...)
			break
		}
	}
	return
}

func TimeStrSub(str string, days int) string {
	t, _ := time.Parse("2006-01-02", str)
	tm := t.AddDate(0, 0, days)
	return tm.Format("2006-01-02")
}
